var eventFactory = require('./eventFactory'),
    DataTransfer = require('./DataTransfer');

function _noop() {}

function parseParams(targetElement, eventProperties, configCallback) {
    if (typeof eventProperties === 'function') {
        configCallback = eventProperties;
        eventProperties = null;
    }

    if (!targetElement || typeof targetElement !== 'object') {
        throw new Error('Expected first parameter to be a targetElement. Instead got: ' + targetElement);
    }

    return {
        targetElement: targetElement,
        eventProperties: eventProperties || {},
        configCallback: configCallback || _noop,
    };
}

function customizeEvent(event, configCallback, isPrimaryEvent) {
    if (configCallback) {
        // call configCallback only for the primary event if the callback takes less than two arguments
        if (configCallback.length < 2) {
            if (isPrimaryEvent) {
                configCallback(event);
            }
        }
        // call configCallback for each event if the callback takes two arguments
        else {
            configCallback(event, event.type);
        }
    }
}

function createAndDispatchEvents(targetElement, eventNames, primaryEventName, dataTransfer, eventProperties, configCallback) {
    eventNames.forEach(function (eventName) {
        var event = eventFactory.createEvent(eventName, eventProperties, dataTransfer);
        var isPrimaryEvent = eventName === primaryEventName;

        customizeEvent(event, configCallback, isPrimaryEvent);

        targetElement.dispatchEvent(event);
    });
}

var DragDropAction = function () {
    this.lastDragSource = null;
    this.lastDataTransfer = null;
    this.pendingActionsQueue = [];
};

DragDropAction.prototype._queue = function (fn) {
    this.pendingActionsQueue.push(fn);

    if (this.pendingActionsQueue.length === 1) {
        this._queueExecuteNext();
    }
};

DragDropAction.prototype._queueExecuteNext = function () {
    if (this.pendingActionsQueue.length === 0) {
        return;
    }

    var self = this;
    var firstPendingAction = this.pendingActionsQueue[0];

    var doneCallback = function () {
        self.pendingActionsQueue.shift();
        self._queueExecuteNext();
    };

    if (firstPendingAction.length === 0) {
        firstPendingAction.call(this);
        doneCallback();
    } else {
        firstPendingAction.call(this, doneCallback);
    }
};

DragDropAction.prototype.dragStart = function (targetElement, eventProperties, configCallback) {
    var params = parseParams(targetElement, eventProperties, configCallback),
        events = ['mousedown', 'dragstart', 'drag'],
        dataTransfer = new DataTransfer();

    this._queue(function () {
        createAndDispatchEvents(params.targetElement, events, 'drag', dataTransfer, params.eventProperties, params.configCallback);

        this.lastDragSource = targetElement;
        this.lastDataTransfer = dataTransfer;
    });

    return this;
};

DragDropAction.prototype.dragEnter = function (overElement, eventProperties, configCallback) {
    var params = parseParams(overElement, eventProperties, configCallback),
        events = ['mousemove', 'mouseover', 'dragenter'];

    this._queue(function () {
        createAndDispatchEvents(params.targetElement, events, 'dragenter', this.lastDataTransfer, params.eventProperties, params.configCallback);
    });

    return this;
};

DragDropAction.prototype.dragOver = function (overElement, eventProperties, configCallback) {
    var params = parseParams(overElement, eventProperties, configCallback),
        events = ['mousemove', 'mouseover', 'dragover'];

    this._queue(function () {
        createAndDispatchEvents(params.targetElement, events, 'drag', this.lastDataTransfer, params.eventProperties, params.configCallback);
    });

    return this;
};

DragDropAction.prototype.dragLeave = function (overElement, eventProperties, configCallback) {
    var params = parseParams(overElement, eventProperties, configCallback),
        events = ['mousemove', 'mouseover', 'dragleave'];

    this._queue(function () {
        createAndDispatchEvents(params.targetElement, events, 'dragleave', this.lastDataTransfer, params.eventProperties, params.configCallback);
    });

    return this;
};

DragDropAction.prototype.drop = function (targetElement, eventProperties, configCallback) {
    var params = parseParams(targetElement, eventProperties, configCallback);
    var eventsOnDropTarget = ['mousemove', 'mouseup', 'drop'];
    var eventsOnDragSource = ['dragend'];

    this._queue(function () {
        createAndDispatchEvents(params.targetElement, eventsOnDropTarget, 'drop', this.lastDataTransfer, params.eventProperties, params.configCallback);

        if (this.lastDragSource) {
            // trigger dragend event on last drag source element
            createAndDispatchEvents(this.lastDragSource, eventsOnDragSource, 'drop', this.lastDataTransfer, params.eventProperties, params.configCallback);
        }
    });

    return this;
};

DragDropAction.prototype.then = function (callback) {
    this._queue(function () {
        callback.call(this);
    }); // make sure _queue() is given a callback with no arguments

    return this;
};

DragDropAction.prototype.delay = function (waitingTimeMs) {
    this._queue(function (done) {
        window.setTimeout(done, waitingTimeMs);
    });

    return this;
};

module.exports = DragDropAction;
